using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.CompositeUI.UIElements;
using Microsoft.Practices.CompositeUI.Utility;

namespace SCSFContrib.CompositeUI.WPF.UIElements
{
	/// <summary>
	/// An adapter that wraps an <see cref="ItemsControl"/> for use as an <see cref="IUIElementAdapter"/>.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class allows <see cref="ItemsControl"/>s to be used as hosts for UI elements. As items are added via the
	/// <see cref="Add"/> methods, they are added to the underlying <see cref="ItemsControl"/>. Similarly, removing an item via
	/// the <see cref="Remove"/> method will remove it from the underlying <see cref="ItemsControl"/>.
	/// </para>
	/// <para>
	/// The <c>Index</c> attached property can be used to dictate the order in which items appear in the underlying
	/// <see cref="ItemsControl"/> regardless of the order they are inserted by this <c>ItemsControlUIAdapter</c>. Items with a
	/// larger index will be inserted after items with lesser indexes (and after items with no index at all). Items with no index
	/// will be inserted at the end of the items collection.
	/// </para>
	/// <para>
	/// Note that applying indexes via <see cref="SetIndex"/> after items have been added to the underlying
	/// <see cref="ItemsControl"/> will have no effect. In fact, it may cause incorrect sorting behavior in future inserts.
	/// </para>
	/// </remarks>
	public class ItemsControlUIAdapter : UIElementAdapter<DependencyObject>
	{
		private readonly ItemsControl _itemsControl;

		/// <summary>
		/// Identifies the <c>Index</c> attached property.
		/// </summary>
		public static readonly DependencyProperty IndexProperty = DependencyProperty.RegisterAttached("Index",
			typeof(int?),
			typeof(ItemsControlUIAdapter));

		/// <summary>
		/// Constructs an instance of <c>ItemsControlUIAdapter</c> that wraps the specified <see cref="ItemsControl"/>.
		/// </summary>
		/// <param name="itemsControl">
		/// The <see cref="ItemsControl"/> that this <c>ItemsControlUIAdapter</c> wraps.
		/// </param>
		public ItemsControlUIAdapter(ItemsControl itemsControl)
		{
			Guard.ArgumentNotNull(itemsControl, "itemsControl");
			_itemsControl = itemsControl;
		}

		/// <summary>
		/// Adds the specified item to the <see cref="ItemsControl"/> wrapped by this <c>ItemsControlUIAdapter</c>.
		/// </summary>
		/// <param name="item">
		/// The item to add to the <see cref="ItemsControl"/>.
		/// </param>
		/// <returns>
		/// The added item.
		/// </returns>
		protected override DependencyObject Add(DependencyObject item)
		{
			Guard.ArgumentNotNull(item, "item");
			_itemsControl.Items.Insert(GetInsertionIndex(item), item);
			return item;
		}

		/// <summary>
		/// Removes the specified item from the <see cref="ItemsControl"/> wrapped by this <c>ItemsControlUIAdapter</c>.
		/// </summary>
		/// <param name="item">
		/// The item to remove.
		/// </param>
		protected override void Remove(DependencyObject item)
		{
			Guard.ArgumentNotNull(item, "item");
			_itemsControl.Items.Remove(item);
		}

		/// <summary>
		/// Gets the relative index at which the specified item should be inserted into <see cref="ItemsControl"/>s by the
		/// <c>ItemsControlUIAdapter</c>.
		/// </summary>
		/// <param name="item">
		/// The item whose relative insertion index is to be retrieved.
		/// </param>
		/// <returns>
		/// The relative insertion index, or <see langword="null"/> if no insertion index has been specified for
		/// <paramref name="item"/>.
		/// </returns>
		public static int? GetIndex(DependencyObject item)
		{
			Guard.ArgumentNotNull(item, "item");
			return (int?) item.GetValue(IndexProperty);
		}

		/// <summary>
		/// Sets the relative index at which the specified item should be inserted into <see cref="ItemsControl"/>s by the
		/// <c>ItemsControlUIAdapter</c>.
		/// </summary>
		/// <param name="item">
		/// The item whose relative insertion index is to be set.
		/// </param>
		/// <param name="index">
		/// The relative insertion index. Use <see langword="null"/> to clear any previously set insertion index.
		/// </param>
		public static void SetIndex(DependencyObject item, int? index)
		{
			Guard.ArgumentNotNull(item, "item");
			item.SetValue(IndexProperty, index);
		}

		private int GetInsertionIndex(DependencyObject item)
		{
			int? relativeInsertionIndex = GetIndex(item);

			if (!relativeInsertionIndex.HasValue)
			{
				//no relative insertion index applied, so just insert at end
				return _itemsControl.Items.Count;
			}

			//relative insertion index is applied - need to find actual insertion index based on current items in the ItemsControl
			for (int i = 0; i < _itemsControl.Items.Count; ++i)
			{
				DependencyObject dependencyObject = _itemsControl.Items[i] as DependencyObject;

				if (dependencyObject != null)
				{
					int? dependencyObjectRelativeInsertionIndex = GetIndex(dependencyObject);

					if (dependencyObjectRelativeInsertionIndex.HasValue && dependencyObjectRelativeInsertionIndex.Value > relativeInsertionIndex.Value)
					{
						return i;
					}
				}
			}

			//nothing found with a greater relative insertion index, so insert at end
			return _itemsControl.Items.Count;
		}
	}
}